﻿// Copyright (c) 2012 Francis Xavier Joseph Pulikotil
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Daabli
{
    public static class ContainerReaderHelper
    {
        public static object ReadElement(Type type, Reader r)
        {
            var element = Reader.TryCreateObject(type);
            // If the element could not be created, then a dynamically created object is expected in the input.

            if (!r.Read(ref element))
                return null;

            var elementType = element.GetType();
            if (!type.IsAssignableFrom(elementType))
            {
                r.Log.Write(MessageType.Error, "'{0}' is not a kind of '{1}'", elementType, type);
                return null;
            }

            return element;
        }
    }

    public class ListReader : IObjectReader
    {
        public Type Type { get { return typeof(List<>); } }
        public Grouping Grouping { get { return Daabli.Grouping.Required; } }

        public object Read(object obj, Reader r)
        {
            Debug.Assert(obj != null);

            if (r.PeekGroupFooter())
                return obj;

            var containerType = obj.GetType();
            Debug.Assert(containerType.IsGenericTypeExt());
            Debug.Assert(containerType.GetGenericTypeDefinition() == Type);

            var elementType = containerType.GetGenericArguments()[0];

            var container = obj as IList;
            while (true)
            {
                var element = ContainerReaderHelper.ReadElement(elementType, r);
                if (element == null)
                    return null;

                container.Add(element);

                if (!r.PeekSeparator())
                    break;

                if (!r.ReadSeparator())
                    return null;
            }

            return obj;
        }
    }

    public class DictionaryReader : IObjectReader
    {
        public Type Type { get { return typeof(Dictionary<,>); } }
        public Grouping Grouping { get { return Daabli.Grouping.Required; } }

        public object Read(object obj, Reader r)
        {
            Debug.Assert(obj != null);

            if (r.PeekGroupFooter())
                return obj;

            var containerType = obj.GetType();
            Debug.Assert(containerType.IsGenericTypeExt());
            Debug.Assert(containerType.GetGenericTypeDefinition() == Type);

            var keyType = containerType.GetGenericArguments()[0];
            var valueType = containerType.GetGenericArguments()[1];

            var container = obj as IDictionary;
            while (true)
            {
                if (!r.ReadGroupHeader())
                    return null;

                var key = ContainerReaderHelper.ReadElement(keyType, r);
                if (key == null)
                    return null;

                if (!r.ReadSeparator())
                    return null;

                var value = ContainerReaderHelper.ReadElement(valueType, r);
                if (value == null)
                    return null;

                if (!r.ReadGroupFooter())
                    return null;

                if (container.Contains(key))
                {
                    r.Log.Write(MessageType.Error, "dictionary cannot contain duplicate keys: {0}", key);
                    return null;
                }

                container.Add(key, value);

                if (!r.PeekSeparator())
                    break;

                if (!r.ReadSeparator())
                    return null;
            }

            return obj;
        }
    }

    public class QueueReader : IObjectReader
    {
        #region Queue interface
        private interface IQueue
        {
            void Enqueue(object element);
        }
        private class QueueContainer<T> : IQueue
        {
            private Queue<T> _container;

            public QueueContainer(object container)
            {
                _container = container as Queue<T>;
                Debug.Assert(_container != null);
            }
            public void Enqueue(object element)
            {
                Debug.Assert(element != null);
                Debug.Assert(element.GetType() == typeof(T));

                var item = (T)element;
                _container.Enqueue(item);
            }
        }
        private IQueue GetQueueInterface(object queue, Type elementType)
        {
            Debug.Assert(queue != null);
            Debug.Assert(elementType != null);

            var containerType = typeof(QueueContainer<>).MakeGenericType(elementType);

            IQueue result = null;
            Reader.NoThrowAction(() => { result = Activator.CreateInstance(containerType, queue) as IQueue; });

            return result;
        }
        #endregion

        public Type Type { get { return typeof(Queue<>); } }
        public Grouping Grouping { get { return Daabli.Grouping.Required; } }

        public object Read(object obj, Reader r)
        {
            Debug.Assert(obj != null);

            if (r.PeekGroupFooter())
                return obj;

            var containerType = obj.GetType();
            Debug.Assert(containerType.IsGenericTypeExt());
            Debug.Assert(containerType.GetGenericTypeDefinition() == Type);

            var elementType = containerType.GetGenericArguments()[0];

            var queue = GetQueueInterface(obj, elementType);
            if (queue == null)
                return null;

            while (true)
            {
                var element = ContainerReaderHelper.ReadElement(elementType, r);
                if (element == null)
                    return null;

                queue.Enqueue(element);

                if (!r.PeekSeparator())
                    break;

                if (!r.ReadSeparator())
                    return null;
            }

            return obj;
        }
    }

    public class StackReader : IObjectReader
    {
        #region Stack interface
        private interface IStack
        {
            void Push(object element);
        }
        private class StackContainer<T> : IStack
        {
            private Stack<T> _container;

            public StackContainer(object container)
            {
                _container = container as Stack<T>;
                Debug.Assert(_container != null);
            }
            public void Push(object element)
            {
                Debug.Assert(element != null);
                Debug.Assert(element.GetType() == typeof(T));

                var item = (T)element;
                _container.Push(item);
            }
        }
        private IStack GetStackInterface(object stack, Type elementType)
        {
            Debug.Assert(stack != null);
            Debug.Assert(elementType != null);

            var containerType = typeof(StackContainer<>).MakeGenericType(elementType);

            IStack result = null;
            Reader.NoThrowAction(() => { result = Activator.CreateInstance(containerType, stack) as IStack; });

            return result;
        }
        #endregion

        public Type Type { get { return typeof(Stack<>); } }
        public Grouping Grouping { get { return Daabli.Grouping.Required; } }

        public object Read(object obj, Reader r)
        {
            Debug.Assert(obj != null);

            if (r.PeekGroupFooter())
                return obj;

            var containerType = obj.GetType();
            Debug.Assert(containerType.IsGenericTypeExt());
            Debug.Assert(containerType.GetGenericTypeDefinition() == Type);

            var elementType = containerType.GetGenericArguments()[0];

            // Note: We want the elements in the stack to appear in the same order as in the input.
            //       But since Stack only allows push operations, we'll first load all the elements
            //       into a list, and then push them in reverse order into the stack.

            var list = new List<object>();
            while (true)
            {
                var element = ContainerReaderHelper.ReadElement(elementType, r);
                if (element == null)
                    return null;

                list.Add(element);

                if (!r.PeekSeparator())
                    break;

                if (!r.ReadSeparator())
                    return null;
            }

            var stack = GetStackInterface(obj, elementType);
            if (stack == null)
                return null;

            for (int i = (list.Count - 1); i >= 0; --i)
                stack.Push(list[i]);

            return obj;
        }
    }

    public class LinkedListReader : IObjectReader
    {
        #region LinkedList interface
        private interface ILinkedList
        {
            void AddLast(object element);
        }
        private class LinkedListContainer<T> : ILinkedList
        {
            private LinkedList<T> _container;

            public LinkedListContainer(object container)
            {
                _container = container as LinkedList<T>;
                Debug.Assert(_container != null);
            }
            public void AddLast(object element)
            {
                Debug.Assert(element != null);
                Debug.Assert(element.GetType() == typeof(T));

                var item = (T)element;
                _container.AddLast(item);
            }
        }
        private ILinkedList GetLinkedListInterface(object linkedList, Type elementType)
        {
            Debug.Assert(linkedList != null);
            Debug.Assert(elementType != null);

            var containerType = typeof(LinkedListContainer<>).MakeGenericType(elementType);

            ILinkedList result = null;
            Reader.NoThrowAction(() => { result = Activator.CreateInstance(containerType, linkedList) as ILinkedList; });

            return result;
        }
        #endregion

        public Type Type { get { return typeof(LinkedList<>); } }
        public Grouping Grouping { get { return Daabli.Grouping.Required; } }

        public object Read(object obj, Reader r)
        {
            Debug.Assert(obj != null);

            if (r.PeekGroupFooter())
                return obj;

            var containerType = obj.GetType();
            Debug.Assert(containerType.IsGenericTypeExt());
            Debug.Assert(containerType.GetGenericTypeDefinition() == Type);

            var elementType = containerType.GetGenericArguments()[0];

            var linkedList = GetLinkedListInterface(obj, elementType);
            if (linkedList == null)
                return null;

            while (true)
            {
                var element = ContainerReaderHelper.ReadElement(elementType, r);
                if (element == null)
                    return null;

                linkedList.AddLast(element);

                if (!r.PeekSeparator())
                    break;

                if (!r.ReadSeparator())
                    return null;
            }

            return obj;
        }
    }
}
